
package visualisation;

import primitives.graph.*;
import primitives.cluster.*;
import translator.DOTWriter;
import search.util.*;
import java.awt.image.BufferedImage
import java.awt.*
import javax.imageio.*

class QDlayout{
	
	def dimension_x = 2560
	def dimension_y = 1600
	def scale = 3
	private BufferedImage bi
	
	def random_x(){
		return (int)(Math.random() * (dimension_x + 1))
	}
	def random_y(){
		return (int)(Math.random() * (dimension_y + 1))
	}

	ClusterLeaf c;
	
	ClusterHead tree;
	
	def static outEdges(IClusterLevel c){
        return edgesFilteredBy(c,{
                trans -> 
                !c.nodes.contains(trans.destinationNode)

        });
    }

	def static intraEdges(IClusterLevel c){
		return edgesFilteredBy(c,{
                trans -> 
                c.nodes.contains(trans.destinationNode)

        });
	}

	def static edgesFilteredBy(IClusterLevel c, clj){
		Collection<Transition> nodes = []
        c.nodes.each{
            node ->
            nodes.addAll(node.getTransitionsAsT().findAll(clj))
        }
        return nodes
	}

	def static weighted_random(weight){
		
		if(weight.size() == 1) return weight[0]
		

		
		def sum = weight.sum(0)
		
		def rand = Math.random() * sum
		
		for(int i =0;i<weight.size(); i++){
			if(rand < weight[i]) return weight[i]
			rand -= weight[i]
		}
		println "SHOULDNT HAVE HAPPENED"
	}

	
	def select_one(population){

		def bins = rank(population)
		


		/*
		bins = {
			fitness : [solution,solution,solution],
			fitness : [solution]
		}
		*/
		def selector = bins.keySet().sort().reverse()
		

		//get a weighted random number here
		
		def bin = weighted_random(selector)

		
		def item = bins[bin][(int) Math.round (Math.random() * (bins[bin].size() -1)) ]
		
		return item

		
		
	}
	
	def select_pair(population){
		def one = select_one(population)
		def two = select_one(population)
		
		return [one,two]

	}
	
	def mutate(individual){
		def mutant = new Assignments();
		def item = Math.round(Math.random() * (tree.nodes.size() - 1))
		tree.nodes.each{
			mutant.node_placements[it] = individual.node_placements[it].copy();
			if(Math.random() <= 0.3){
				def mult = 1
				if(Math.random() > 0.5) mult = -1
				if(Math.random() > 0.5){
					def current_x = mutant.node_placements[it].x

					
					current_x = Math.round(Math.abs( current_x + (mult * Math.random() * dimension_x )) % dimension_x)
					mutant.node_placements[it].x = current_x
					
					
				}else{
					def current_y = mutant.node_placements[it].y
					current_y = Math.round(Math.abs( current_y + (mult * Math.random() * dimension_y )) % dimension_y)
					mutant.node_placements[it].y = current_y
				}
			}
		}
		return mutant
	}
	
	def crossover(one, two){
		def child1 = new Assignments();
		def child2 = new Assignments();
		def last = false;
		
		tree.children.each{
			ch ->
			ch.nodes.each{
				if(last){
					child1.node_placements[it] = one.node_placements[it].copy();
					child2.node_placements[it] = two.node_placements[it].copy();

				}else{
					child1.node_placements[it] = two.node_placements[it].copy();
					child2.node_placements[it] = one.node_placements[it].copy();

				}
			}
			last = !last

		}
		
		tree.nodes.each{
			if(last){
				child1.node_placements[it] = one.node_placements[it].copy();
				child2.node_placements[it] = two.node_placements[it].copy();
				last = false;
			}else{
				child1.node_placements[it] = two.node_placements[it].copy();
				child2.node_placements[it] = one.node_placements[it].copy();
				last = true;
			}
		}
		return [child1,child2]
	}
	
	static def adjacent(Position position){
		def ret = []

		[-1,0,1].each{
			x ->
			[-1,0,1].each{
				y ->
				if(x != 0 && y != 0){
					def x_ = position.x+x
					def y_ = position.y+y
					
					if(x_ > 0 && y_ > 0)
						ret.add(new Position(x_,y_));
				}
			}
		}
		return ret;
		
	}
	def distance(pos_d, pos_i){
		def dx = pos_d.x - pos_i.x
		def dy = pos_d.y - pos_i.y
		
		return Math.sqrt(dx*dx + dy*dy)
	}
	def fitness(individual){

		def taken = [:]
		double fitness = 0;
		def none = true;
		Position average = new Position(0,0)
		
		def boundingBoxes = []
		
		tree.children.each{
			ch ->
			def positions = []
			
			def lowest = new Position(1000000,100000)
			def highest = new Position(-1,-1)
			
			ch.nodes.each{

				positions.add(individual.node_placements[it])
				def pos_i = individual.node_placements[it]
				
				average.x += pos_i.x
				average.y += pos_i.y
				
				if(taken[pos_i.y*dimension_y  + pos_i.x] == null){
					taken[pos_i.y*dimension_y  + pos_i.x] = true;
					//fitness += 5
				}else{

					none = false;
				}
				it.transitionsAsT.each{
					tx ->
					def dest = tx.destinationNode;
					def pos_d = individual.node_placements[dest]
					
					
					//fitness -= distance(pos_i,pos_d)
				}
			}
			
			if(none){
				fitness += 5
			}
			
			average.x = average.x / (double)ch.nodes.size()
			average.y = average.y / (double)ch.nodes.size()
			
			def required = []
			def d = 0
			positions.each{
				required.add(adjacent(it))
				d += distance(it,average)
				if(it.x > highest.x)
					highest.x = it.x
				if(it.y > highest.y)
					highest.y = it.y
				if(it.x < lowest.x)
					lowest.x = it.x
				if(it.y < lowest.y)
					lowest.y = it.y
			}
			if(ch.nodes.size() >= 3){

				boundingBoxes.add(new BoundingBox(highest,lowest))
				def bb_size = Math.abs(highest.x - lowest.x) * Math.abs(highest.y - lowest.y)
			
				//fitness += 1/(bb_size + 0.1)
			}

			def adjacent_count = 0.0
			required.each{
				req ->
				req.each{
					adjacent_slot ->
					positions.each{
						if(it.x != adjacent_slot.x && it.y != adjacent_slot.y){
							
						}else{
							adjacent_count++ 
						}
					}
				}
			}
			
			def proportion = adjacent_count / ch.nodes.size()
			//fitness += proportion
		}
		def hits = 0
		def out_of = 0
		def overlap = 0
		boundingBoxes.each{
			outer->

			boundingBoxes.each{
				inner->
					if(!(inner.lo.equals(outer.lo) && inner.hi.equals(outer.hi) ) ){
						def r1 = new Rectangle((int)outer.lo.x, (int)outer.lo.y, (int)(outer.hi.x - outer.lo.x), (int)(outer.hi.y - outer.lo.y))
						def r2 = new Rectangle((int)inner.lo.x, (int)inner.lo.y, (int)(inner.hi.x - inner.lo.x), (int)(inner.hi.y - inner.lo.y))
						
					
						out_of++;
						
						
						if(r1.intersects (r2)){
							hits++
						
							def is = r1.intersection(r2)

							overlap += Math.abs(is.getWidth() * is.getHeight() + 0.1) 
							/*
							println r1
							println r2
							println is
							*/
						}
					}
			}
		}

		if(hits > 0)
			fitness += (dimension_x * dimension_y) / (overlap * hits)
		else
			fitness += Integer.MAX_VALUE
		
		if(fitness <= -1){
			println "Fittness of $fitness is ILLEGAL and will be clamped at 0"
			return 0
		}
		return fitness
		
		
		
	}
	
	static boolean intersects(lo1,hi1, lo2,hi2){
		
		return (hi1.x > lo2.x && hi1.y > lo2.y) || (hi2.x > lo1.x && hi2.y > lo1.y)
		
	}
	
	def rank(population){
			def bins = [:]
			population.each{
				def fitness = fitness(it);
				if(bins[fitness] == null) bins[fitness] = []
				bins[fitness].add(it);
			}
			return bins
	}
	
	public QDlayout(){
		bi = new BufferedImage(scale*dimension_x,scale*dimension_y,BufferedImage.TYPE_INT_RGB);

		
	}
	
	def doLayout(ClusterHead tree){
		this.tree = tree;
		def popsize = 75
		def population = []
		def crossover_p = 0.8
		def mutation_p = 0.1
		
		def generations = 150
		
		println "Starting new layout run pop: $popsize, generations: $generations"
		
		for(int i =0; i<popsize; i++){
			def current = new Assignments();
			
			def numC = tree.children.size()
			
			
			
			tree.children.each{
				ch ->
				ch.nodes.each{
					current.node_placements.put(it, new Position(random_x(),random_y()));
				}
			}
			population.add(current)
		}
		
		def new_pop = []
		
		for(int i=0; i<generations; i++){
			println "Step"

			if(Math.random() > (1- crossover_p)){


				
				
				def (p1,p2) = select_pair(population)
				def (c1,c2) = crossover(p1,p2)
				population.add(c1)
				population.add(c2)
			}
			if(Math.random() > (1 - mutation_p)){
				population.add(mutate(select_one(population)))
				
			}
			def greatest = -Integer.MAX_VALUE;
			def bins = rank(population)
			if(population.size() > popsize + 20){
				bins.keySet().sort().each{
					if(it > greatest) greatest = it
					if(population.size() > popsize)
					
						bins[it].each{
							ind ->
							if(population.size() > popsize)
								population = population -ind
					}
				}
				if(bins[greatest] == null){
					println "BINS GREATEST IS NULL WHY??"
					println bins
				}
				dump_individual(bins[greatest][0], tree, "${greatest}_${i}.png")
				
			}
			
			
		}
		def bins = rank(population)
		def fittest = bins.keySet().sort().reverse()[0]
		println "Ranking done, fittest soloution has value: $fittest"
		return bins[fittest][0]
		
		
	}
	
	static double mag(vec){
		return Math.sqrt(vec.collect{
			it * it
		}.sum())
	}
	
	static def normalise(vec){
		def mg = mag(vec)
		return vec.collect{
			it/mg
		}
	}
	
	static def dot(vec1, vec2){
		def sum = 0
		for(int i =0; i<vec1.size(); i++){
			sum += vec1[i] * vec2[i]
		}
		return sum
	}
	
	static def vector(Position point1, Position point2){
		def xdiff = point2.x - point1.x
		def ydiff = point2.y - point1.y
		

		return [xdiff, ydiff]
	}
	

	
	static double angle_between(Position point1, Position point2){
		
		def xdiff = point2.x - point1.x
		def ydiff = point2.y - point1.y
		
		def vec1 = [0,1]
		def vec2 = [xdiff, ydiff]
		
		vec2 = normalise(vec2)
		
		def angle = 0.0;
		

		if(xdiff >= 0){
			if(ydiff >= 0){
				
				angle+=0*90
			}else{
				vec1 = [1,0]
				angle+=1*90
			}
		}else{
			if(ydiff >= 0){
				vec1 = [-1,0]
				angle+=3*90
			}else{
				vec1 = [0,-1]
				angle+=2*90
			}
		}
		angle+=Math.toDegrees(Math.acos(dot(vec1,vec2)))
		
		return angle
		
	}
	
	def  dump_individual(layout, tree, filename){
						

		
			
				Graphics2D g2 = bi.createGraphics();
				g2.setBackground(Color.WHITE);
				g2.clearRect(0,0,10*dimension_x,10*dimension_y)
				g2.setColor(Color.BLACK)
				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,  RenderingHints.VALUE_ANTIALIAS_ON);
			def count =0



					def done1 = false
					tree.children.each{


							def points = []

							Polygon cluster_boundary = new Polygon()
							def leftmost = new Point(10000000,1)
							it.nodes.each{
								n->
								def ps = layout.node_placements[n]
								def adj = adjacent(ps)
								def pos = ps//				adj.each{ pos->
									points.add(pos)
									if(pos.x < leftmost.x){
										leftmost = pos
									}
			//					}

							}
							cluster_boundary.addPoint((int)(scale*leftmost.x),(int)(scale*leftmost.y))


							def c = 0
							def current_point = leftmost
							def one = true
							def stop = false

							def last_point = null

							while(!stop && !points.isEmpty()){
								c++
								def lowest = null;
								def lowest_angle = 361
								points.each{
									if(lowest == null) lowest = it;
									def angle = 0
									angle = angle_between(current_point, it)
									if(last_point != null){
										def angle_to_backvec = angle_between(current_point, last_point)

										if(angle <= angle_to_backvec){
											angle = 360 + (angle - angle_to_backvec)
										}else{
											angle = angle - angle_to_backvec
										}

									}
									if(it.x != current_point.x && it.y != current_point.y && angle < lowest_angle){
										lowest_angle = angle


										lowest = it;
									}

								}
								if(lowest.x != leftmost.x && lowest.y != leftmost.y){
									cluster_boundary.addPoint((int)(scale*lowest.x),(int)(scale*lowest.y))
									points = points - lowest
									last_point = current_point
									current_point = lowest
								}else{

									stop = true;
									cluster_boundary.addPoint((int)(scale*lowest.x),(int)(scale*lowest.y))
								}



							}


							Color pn = Color.PINK
							Color pink = new Color(pn.getRed(), pn.getGreen(), pn.getBlue(),31)
							g2.setColor(pink)

							g2.fill(cluster_boundary)
							def isLoner = false
							if(it.nodes.size() <= 2){
								isLoner = true
							}

							g2.setColor(new Color(rand_C(), rand_C(), rand_C()))


							it.nodes.each{
							n ->
								def pos = layout.node_placements[n]
								def size = 10;
								def boxSize = 3*size
								count++;

								if(isLoner){
									g2.setColor(pink)

									g2.fillRect((int)(scale* pos.x - (0.5*boxSize)),(int)(scale*pos.y - (0.5*boxSize)), (int)(boxSize), (int)(boxSize))

									g2.setColor(new Color(rand_C(), rand_C(), rand_C()))
								}

								g2.fillOval((int)(scale* pos.x - (0.5*size)),(int)(scale*pos.y - (0.5*size)), size, size)

								n.transitionsAsT.each{
									tx ->
									def dest = tx.destinationNode;
									def pos_d = layout.node_placements[dest]

									g2.drawLine((int)(scale*pos.x),(int)(scale*pos.y),(int)(scale*pos_d.x),(int)(scale*pos_d.y))

								}
							}


					}
			
					println "painted $count points"
					try {

					    File outputfile = new File(filename);
					    ImageIO.write(bi, "png", outputfile);
					} catch (IOException e) {

					}

		
	}
	
	public static void main(String[] args){
		def filename = args[0]

		ClusterHead tree = TreeLoader2.loadTreeFromSerial(new File(filename))

		def l = new QDlayout();


		def layout = l.doLayout(tree)

		
		l.dump_individual(layout,tree, "out.png")

		

		
		


		
		
	}
	
	static def rand_C(){
		return (int)(Math.random() * 256)
	}
	
}